using System;
using System.Collections.Generic;
using System.Text;
using System.Messaging;
using System.Globalization;

namespace SMS.SportAutoService.MQ
{
    /// <summary>
    /// the information we return after we installed a message queue listener; it provides the message
    /// queue itself plus the handle to the AsyncResult object
    /// </summary>
    public struct MessageListener
    {
        public MessageQueue MQueue;
        public IAsyncResult AsyncResult;
    }

    /// <summary>
    /// this class provides support to work with MS message queue; it provides members to create
    /// new queues, enumerate the current ones, send new messages, receive messages, etc.
    /// </summary>
    public class MessageQueueManager
    {
        private const string MachinePrefix = @".\";
        private const string PrivateQueueNamePrefix = MachinePrefix + @"Private$\";
        private const string Sepearator = ": ";
        private const string AuthenticateProperty = "Authenticate";
        private const string BasePriorityProperty = "BasePriority";
        private const string CanReadProperty = "CanRead";
        private const string CanWriteProperty = "CanWrite";
        private const string CategoryProperty = "Category";
        private const string CreateTimeProperty = "CreateTime";
        private const string DenySharedReceiveProperty = "DenySharedReceive";
        private const string EncryptionRequiredProperty = "EncryptionRequired";
        private const string FormatNameProperty = "FormatName";
        private const string LabelProperty = "Label";
        private const string LastModifyTimeProperty = "LastModifyTime";
        private const string MachineNameProperty = "MachineName";
        private const string MaximumJournalSizeProperty = "MaximumJournalSize";
        private const string MaximumQueueSizeProperty = "MaximumQueueSize";
        private const string MulticastAddressProperty = "MulticastAddress";
        private const string PathProperty = "Path";
        private const string QueueNameProperty = "QueueName";
        private const string ReadHandleProperty = "ReadHandle";
        private const string TransactionalProperty = "Transactional";
        private const string UseJournalQueueProperty = "UseJournalQueue";
        private const string WriteHandleProperty = "WriteHandle";
        private const string MessageIdProperty = "MessageId";
        private const string AcknowledgeTypeProperty = "AcknowledgeType";
        private const string AcknowledgmentProperty = "Acknowledgment";
        private const string AppSpecificProperty = "AppSpecific";
        private const string ArrivedTimeProperty = "ArrivedTime";
        private const string AttachSenderIdProperty = "AttachSenderId";
        private const string AuthenticatedProperty = "Authenticated";
        private const string AuthenticationProviderNameProperty = "AuthenticationProviderName";
        private const string AuthenticationProviderTypeProperty = "AuthenticationProviderType";
        private const string BodyTypeProperty = "BodyType";
        private const string ConnectorTypeProperty = "ConnectorType";
        private const string CorrelationIdProperty = "CorrelationId";
        private const string EncryptionAlgorithmProperty = "EncryptionAlgorithm";
        private const string IsFirstInTransactionProperty = "IsFirstInTransaction";
        private const string IsLastInTransactionProperty = "IsLastInTransaction";
        private const string LookupIdProperty = "LookupId";
        private const string MessageTypeProperty = "MessageType";
        private const string PriorityProperty = "Priority";
        private const string RecoverableProperty = "Recoverable";
        private const string SenderVersionProperty = "SenderVersion";
        private const string SentTimeProperty = "SentTime";
        private const string TimeToBeReceivedProperty = "TimeToBeReceived";
        private const string TimeToReachQueueProperty = "TimeToReachQueue";
        private const string SenderIdPropertTransactionIdPropertyy = "SenderIdPropertTransactionId";
        private const string TransactionStatusQueueProperty = "TransactionStatusQueue";
        private const string UseAuthenticationProperty = "UseAuthentication";
        private const string UseDeadLetterQueueProperty = "UseDeadLetterQueue";
        private const string UseEncryptionProperty = "UseEncryption";
        private const string UseTracingProperty = "UseTracing";
        private const string TransactionIdProperty = "TransactionId";

        /// <summary>
        /// create a private comnstructor so no instance of this class can be created; all 
        /// members provided are static
        /// </summary>
        private MessageQueueManager()
        {
        }

        /// <summary>
        /// create a private message queue; does not do anything if the message queue already exists
        /// </summary>
        /// <param name="QueueName">the name of the new message queue</param>
        /// <param name="LabelName">the label of the new message queue</param>
        /// <param name="Transactional">is the message queue transactional</param>
        public static void CreatePrivateMessageQueue(string QueueName, string LabelName, bool Transactional)
        {
            // check to make sure the message queue does not exist already
            if (!MessageQueue.Exists(PrivateQueueNamePrefix + QueueName))
            {
                // create the new message queue and make it transactional
                MessageQueue MQ = MessageQueue.Create(PrivateQueueNamePrefix + QueueName, Transactional);

                // set the label name and close the message queue
                MQ.Label = LabelName;
                MQ.Close();
            }
        }

        /// <summary>
        /// delete the private message queue
        /// </summary>
        /// <param name="QueuePath">the name full path of the message queue</param>
        public static void DeletePrivateMessageQueue(string QueuePath)
        {
            // check to make sure the message queue does exist 
            if (MessageQueue.Exists(QueuePath))
            {
                try
                {
                    MessageQueue.Delete(QueuePath);
                }
                catch (MessageQueueException)
                {
                    return;
                }
            }
        }

        /// <summary>
        /// return a list with all the private queues on the specified machine
        /// </summary>
        /// <param name="MachineName">the machine to look at; uses the current machine if set to NULL</param>
        /// <returns>returns an array of message queue names and paths</returns>
        public static string[,] GetListOfPrivateMessageQueues(string MachineName)
        {
            // use the current machine name if no other specified
            if ((MachineName == null) || (MachineName.Length <= 0))
                MachineName = Environment.MachineName;

            // get the list of message queues
            MessageQueue[] MQList = MessageQueue.GetPrivateQueuesByMachine(MachineName);

            // we did not find any private message queues on this machine
            if (MQList.Length <= 0)
                return null;

            // otherwise get the list of names and path's and return
            else
            {
                // allocate a string array which can return for each message queue the name
                // and the path
                string[,] MQNameList = new string[MQList.Length, 3];

                // loop through all message queues and get the name and path to return
                for (int Count = 0; Count < MQList.Length; Count++)
                {
                    MQNameList[Count, 0] = MachinePrefix + MQList[Count].QueueName;
                    MQNameList[Count, 1] = MQList[Count].Label;
                    MQNameList[Count, 2] = MQList[Count].Transactional.ToString();
                }

                // return the string array with the queue names and paths
                return MQNameList;
            }
        }

        /// <summary>
        /// retrieve all the properties of the message queue and return as a array of
        /// string values
        /// </summary>
        /// <param name="QueuePath">the message queue to look at</param>
        /// <returns>returns the queue properties as an array of string values</returns>
        public static string[] GetMessageQueueProperties(string QueuePath)
        {
            // if no message queue path provided 
            if ((QueuePath == null) || (QueuePath.Length <= 0))
                return null;

            // open the message queue
            MessageQueue MQueue = new MessageQueue(QueuePath);

            // stores all the message queue properties
            string[] MQProperties = new string[21];

            // get all the properties
            MQProperties[0] = AuthenticateProperty + Sepearator + MQueue.Authenticate.ToString();
            MQProperties[1] = BasePriorityProperty + Sepearator + MQueue.BasePriority.ToString();
            MQProperties[2] = CanReadProperty + Sepearator + MQueue.CanRead.ToString();
            MQProperties[3] = CanWriteProperty + Sepearator + MQueue.CanWrite.ToString();
            MQProperties[4] = CategoryProperty + Sepearator + MQueue.Category.ToString();
            MQProperties[5] = CreateTimeProperty + Sepearator + MQueue.CreateTime.ToString();
            MQProperties[6] = DenySharedReceiveProperty + Sepearator + MQueue.DenySharedReceive.ToString();
            MQProperties[7] = EncryptionRequiredProperty + Sepearator + MQueue.EncryptionRequired.ToString();
            MQProperties[8] = FormatNameProperty + Sepearator + MQueue.FormatName;
            MQProperties[9] = LabelProperty + Sepearator + MQueue.Label;
            MQProperties[10] = LastModifyTimeProperty + Sepearator + MQueue.LastModifyTime.ToString();
            MQProperties[11] = MachineNameProperty + Sepearator + MQueue.MachineName;
            MQProperties[12] = MaximumJournalSizeProperty + Sepearator + MQueue.MaximumJournalSize.ToString();
            MQProperties[13] = MaximumQueueSizeProperty + Sepearator + MQueue.MaximumQueueSize.ToString();
            //MQProperties[14] = MulticastAddressProperty + Sepearator + MQueue.MulticastAddress;
            MQProperties[15] = PathProperty + Sepearator + MQueue.Path;
            MQProperties[16] = QueueNameProperty + Sepearator + MQueue.QueueName;
            MQProperties[17] = ReadHandleProperty + Sepearator + MQueue.ReadHandle.ToString();
            MQProperties[18] = TransactionalProperty + Sepearator + MQueue.Transactional.ToString();
            MQProperties[19] = UseJournalQueueProperty + Sepearator + MQueue.UseJournalQueue.ToString();
            MQProperties[20] = WriteHandleProperty + Sepearator + MQueue.WriteHandle.ToString();

            // return the array of string values
            return MQProperties;
        }

        /// <summary>
        /// send a message to the specified message queue and also set the response MQ for that message
        /// </summary>
        /// <param name="TheObject">the object data to send</param>
        /// <param name="MQPath">the MQ to place the message</param>
        /// <param name="ResponseMQPath">the MQ to respond</param>
        /// <param name="Label">the label of the message</param>
        /// <param name="MTransactionalQueue">the queue to use to place transactional messages</param>
        /// <param name="Transaction">message queue transaction under which the message is sent</param>
        public static void SendMessage(object TheObject, string MQPath, string ResponseMQPath, string Label, MessageQueue MTransactionalQueue, MessageQueueTransaction Transaction)
        {
            MessageQueue MQueue;

            // create a message queue object or re-use the transactional message queue object
            if (MTransactionalQueue == null)
                MQueue = new MessageQueue(MQPath.Trim());
            else
                MQueue = MTransactionalQueue;

            // create the message and set the base properties
            Message Msg = new Message(TheObject);
            Msg.ResponseQueue = new MessageQueue(ResponseMQPath.Trim());
            Msg.Priority = MessagePriority.Normal;
            Msg.UseJournalQueue = true;
            Msg.UseTracing = true;
            Msg.Label = Label;

            // we want a acknowledgement if received or not in the response queue
            Msg.AcknowledgeType = AcknowledgeTypes.FullReceive;
            Msg.AdministrationQueue = new MessageQueue(ResponseMQPath.Trim());

            // send the message; Transaction specifies the transaction under which to send the message
            if (Transaction == null)
                MQueue.Send(Msg);
            else
                MQueue.Send(Msg, Transaction);

            // close the mesage queue if we created it; we keep the transactional queue we got passed along
            // open till all messages within the transaction are closed
            if (MTransactionalQueue == null)
                MQueue.Close();
        }

        /// <summary>
        /// create a message queue (internal) transaction and start it; return to caller so it can be 
        /// passed along each time we send a message as part of that transaction
        /// </summary>
        /// <returns>return the created transaction</returns>
        public static MessageQueueTransaction StartTransaction()
        {
            // create a message queue transaction and start it
            MessageQueueTransaction Transaction = new MessageQueueTransaction();
            Transaction.Begin();

            // return the created message queue transaction
            return Transaction;
        }

        /// <summary>
        /// returns the list of messages in the passed along message queue
        /// </summary>
        /// <param name="MQPath">the message queue path</param>
        /// <returns>returns a string array with the Label, MessageType, Priority and SendTime of each message</returns>
        public static string[,] GetMessagesOfMessageQueue(string MQPath)
        {
            // open the message queue
            MessageQueue MQueue = new MessageQueue(MQPath);

            // set the properties we want to retrieve
            MQueue.MessageReadPropertyFilter.Id = true;
            MQueue.MessageReadPropertyFilter.Priority = true;
            MQueue.MessageReadPropertyFilter.SentTime = true;
            MQueue.MessageReadPropertyFilter.MessageType = true;
            MQueue.MessageReadPropertyFilter.Label = true;

            // get all the messages; does not remove the message!
            Message[] Msg = MQueue.GetAllMessages();

            // create a string array so we can store the message information to return
            string[,] Messages = new string[Msg.Length, 5];

            // loop through each message and get the info we need to return
            for (int Count = 0; Count < Msg.Length; Count++)
            {
                Messages[Count, 0] = Msg[Count].Id;
                Messages[Count, 1] = Msg[Count].Label;
                Messages[Count, 2] = Msg[Count].MessageType.ToString();
                Messages[Count, 3] = Msg[Count].Priority.ToString();
                Messages[Count, 4] = Msg[Count].SentTime.ToString();
            }

            // close the message queue
            MQueue.Close();

            // return the array with all the message information
            return Messages;
        }

        /// <summary>
        /// get the information of a specific message
        /// </summary>
        /// <param name="MQPath">the message queue path</param>
        /// <param name="MsgId">the message ID</param>
        /// <returns></returns>
        /*public static string[] GetMessageDetails(string MQPath, string MsgId)
        {
            // open the message queue
            MessageQueue MQueue = new MessageQueue(MQPath);

            // we want to retrieve all properties for the message
            MQueue.MessageReadPropertyFilter.SetAll();

            // get a message enumerator we can use to go through all the messages in the queue
            MessageEnumerator Enumerator = MQueue.GetMessageEnumerator2();

            // loop through all the messages 
            while (Enumerator.MoveNext(new TimeSpan(0, 0, 1)))
            {
                // we found the requested message
                if (String.Compare(Enumerator.Current.Id, MsgId, false, CultureInfo.InvariantCulture) == 0)
                {
                    // array we use to return all the string information
                    string[] MsgDetails = new string[31];

                    // get all the message details
                    MsgDetails[0] = MessageIdProperty + Sepearator + Enumerator.Current.Id;
                    MsgDetails[1] = AcknowledgeTypeProperty + Sepearator + Enumerator.Current.AcknowledgeType;
                    MsgDetails[2] = AcknowledgmentProperty + Sepearator + Enumerator.Current.Acknowledgment.ToString();
                    MsgDetails[3] = AppSpecificProperty + Sepearator + Enumerator.Current.AppSpecific.ToString();
                    MsgDetails[4] = ArrivedTimeProperty + Sepearator + Enumerator.Current.ArrivedTime.ToString();
                    MsgDetails[5] = AttachSenderIdProperty + Sepearator + Enumerator.Current.AttachSenderId.ToString();
                    MsgDetails[6] = AuthenticatedProperty + Sepearator + Enumerator.Current.Authenticated.ToString();
                    MsgDetails[7] = AuthenticationProviderNameProperty + Sepearator + Enumerator.Current.AuthenticationProviderName;
                    MsgDetails[8] = AuthenticationProviderTypeProperty + Sepearator + Enumerator.Current.AuthenticationProviderType.ToString();
                    MsgDetails[9] = BodyTypeProperty + Sepearator + Enumerator.Current.BodyType.ToString();
                    MsgDetails[10] = ConnectorTypeProperty + Sepearator + Enumerator.Current.ConnectorType.ToString();
                    MsgDetails[11] = CorrelationIdProperty + Sepearator + Enumerator.Current.CorrelationId;
                    MsgDetails[12] = EncryptionAlgorithmProperty + Sepearator + Enumerator.Current.EncryptionAlgorithm.ToString();
                    MsgDetails[13] = IsFirstInTransactionProperty + Sepearator + Enumerator.Current.IsFirstInTransaction.ToString();
                    MsgDetails[14] = IsLastInTransactionProperty + Sepearator + Enumerator.Current.IsLastInTransaction.ToString();
                    MsgDetails[15] = LabelProperty + Sepearator + Enumerator.Current.Label;
                    MsgDetails[16] = LookupIdProperty + Sepearator + Enumerator.Current.LookupId.ToString();
                    MsgDetails[17] = MessageTypeProperty + Sepearator + Enumerator.Current.MessageType.ToString();
                    MsgDetails[18] = PriorityProperty + Sepearator + Enumerator.Current.Priority.ToString();
                    MsgDetails[19] = RecoverableProperty + Sepearator + Enumerator.Current.Recoverable.ToString();
                    MsgDetails[20] = SenderVersionProperty + Sepearator + Enumerator.Current.SenderVersion.ToString();
                    MsgDetails[21] = SentTimeProperty + Sepearator + Enumerator.Current.SentTime.ToString();
                    MsgDetails[22] = TimeToBeReceivedProperty + Sepearator + Enumerator.Current.TimeToBeReceived.ToString();
                    MsgDetails[23] = TimeToReachQueueProperty + Sepearator + Enumerator.Current.TimeToReachQueue.ToString();
                    MsgDetails[24] = TransactionIdProperty + Sepearator + Enumerator.Current.TransactionId;
                    MsgDetails[25] = TransactionStatusQueueProperty + Sepearator + Enumerator.Current.TransactionStatusQueue.ToString();
                    MsgDetails[26] = UseAuthenticationProperty + Sepearator + Enumerator.Current.UseAuthentication.ToString();
                    MsgDetails[27] = UseDeadLetterQueueProperty + Sepearator + Enumerator.Current.UseDeadLetterQueue.ToString();
                    MsgDetails[28] = UseEncryptionProperty + Sepearator + Enumerator.Current.UseEncryption.ToString();
                    MsgDetails[29] = UseJournalQueueProperty + Sepearator + Enumerator.Current.UseJournalQueue.ToString();
                    MsgDetails[30] = UseTracingProperty + Sepearator + Enumerator.Current.UseTracing.ToString();

                    // close the message queue and return all the message details
                    MQueue.Close();
                    return MsgDetails;
                }
            }

            // close the message queue
            MQueue.Close();

            // we did not find the message so we return null
            return null;
        }
        */
        /// <summary>
        /// starts a message listener for a message queue; the event handler will be called as soon as 
        /// a message is available
        /// </summary>
        /// <param name="MQPath">the message queue path</param>
        /// <param name="MQueueReceiveEventHandler">the event handler called when a message has been received</param>
        /// <param name="TargetTypes">the target types we have stored in the message body</param>
        public static MessageListener StartMessageListener(string MQPath, ReceiveCompletedEventHandler MQueueReceiveEventHandler, Type[] TargetTypes)
        {
            MessageListener MsgListener;

            // it is important to set this to false, otherwise the message receiver event handler
            // keeps still active even after closing the message queue object
            MessageQueue.EnableConnectionCache = false;

            // open the selected message queu
            MessageQueue MQueue = new MessageQueue(MQPath);
            MQueue.MessageReadPropertyFilter.SetAll();

            // the target types we have stored in the message body
            ((XmlMessageFormatter)MQueue.Formatter).TargetTypes = TargetTypes;

            // set the event handler to be called when the message has been received
            MQueue.ReceiveCompleted += MQueueReceiveEventHandler;

            // start the recieve message process and pass along the state information; this call returns 
            // immediately; 
            IAsyncResult MQResult = MQueue.BeginReceive(new TimeSpan(1, 0, 0), MQueue);

            // return the message listener information
            MsgListener.MQueue = MQueue;
            MsgListener.AsyncResult = MQResult;
            return MsgListener;
        }
    }
}